package com.junxonline.instant.admin.shiro;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.junxonline.instant.common.util.common.RedisUtils;
import com.junxonline.instant.dao.entity.model.config.Menu;
import com.junxonline.instant.dao.entity.model.config.Role;
import com.junxonline.instant.dao.entity.model.config.User;
import com.junxonline.instant.dao.mapper.config.MenuMapper;
import com.junxonline.instant.dao.mapper.config.RoleMapper;
import com.junxonline.instant.dao.mapper.config.UserMapper;
import com.junxonline.instant.common.constant.RedisConstant;
import com.junxonline.instant.common.constant.SQLConstant;
import com.junxonline.instant.common.constant.UserInfoConstant;
import com.junxonline.instant.common.enumeration.AdminErrorEnum;
import com.junxonline.instant.common.exception.AdminBizException;
import com.junxonline.instant.common.util.admin.TokenUtils;
import lombok.extern.log4j.Log4j2;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 管理后台认证与授权Realm
 *
 * @author JunX
 * @date 2021-06-27
 */
@Log4j2
@Component
public class AdminRealm extends AuthorizingRealm {

    @Autowired
    private TokenUtils tokenUtils;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    /**
     * 配置shiro支持jwt
     *
     * @param token token
     * @return boolean
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 用户认证逻辑
     *
     * @param authenticationToken 用户凭证信息
     * @return AuthenticationInfo 认证信息
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException, TokenExpiredException {
        // 用户认证
        String token = (String) authenticationToken.getCredentials();
        if (StrUtil.isBlank(token)) {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
        // 获取token中的用户名
        String username = tokenUtils.getUsername(token);
        if (StrUtil.isBlank(username)) {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
        // 根据username查找用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username).last(SQLConstant.LIMIT_1));
        if (user == null) {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
        if (user.getStatus() == 0) {
            throw new AdminBizException(AdminErrorEnum.ACCOUNT_IS_DISABLED);
        }
        if (redisUtils.hHasKey(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_REFRESH_TOKEN)) {
            // 如果token过期
            if (!tokenUtils.verify(token, user.getUsername(), user.getPassword())) {
                throw new TokenExpiredException("Token已过期");
            } else {
                // 获取RefreshToken的时间戳
                String currentTimeMillisRedis = redisUtils.hget(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_REFRESH_TOKEN).toString();
                // 获取Token时间戳与RefreshToken的时间戳对比
                if (tokenUtils.getSignTime(token).toString().equals(currentTimeMillisRedis)) {
                    return new SimpleAuthenticationInfo(token, token, UserInfoConstant.KEY_REALM);
                } else {
                    throw new TokenExpiredException("Token已过期");
                }
            }
        } else {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
    }

    /**
     * 用户授权逻辑
     *
     * @param principalCollection 用户身份集合
     * @return AuthorizationInfo 授权信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String token = principalCollection.toString();
        if (StrUtil.isBlank(token)) {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
        // 获取token中的用户名
        String username = tokenUtils.getUsername(token);
        if (StringUtils.isBlank(username)) {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
        // 根据username查找用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username).last(SQLConstant.LIMIT_1));
        if (user == null) {
            throw new AdminBizException(AdminErrorEnum.AUTH_FAIL);
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        Set<String> roles = new HashSet<>();
        Set<String> permissions = new HashSet<>();
        // 获取用户所有的角色
        List<Role> roleList = roleMapper.selectByUserId(user.getId());
        for (Role role : roleList) {
            roles.add(String.valueOf(role.getId()));
            // 获取该角色的所有权限
            List<Menu> menuList = menuMapper.selectByRoleId(role.getId());
            for (Menu menu: menuList) {
                permissions.add(menu.getPermission());
            }
        }
        // 授权
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

}
